package org.jzoie;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.DefaultSimilarity;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

import proj.zoie.api.ZoieIndexReader;
import proj.zoie.api.indexing.ZoieIndexableInterpreter;
import proj.zoie.impl.indexing.DefaultIndexReaderDecorator;
import proj.zoie.impl.indexing.ZoieConfig;
import proj.zoie.impl.indexing.ZoieSystem;

public class ZoieQuery<T>
{
	private int total=0;
	private Analyzer analyzer=null;
	private String dirPath="/index/jzoie/";
	private Class<T> cls=null;
	public ZoieQuery(Class<T> cls)
	{
		this.cls=cls;
		dirPath=dirPath+"/"+cls.getSimpleName();
		File file=new File(dirPath);
		if (!file.exists())
		{
			file.mkdirs();
		}
	}
	public ZoieQuery(Class<T> cls,String path)
	{
		this.cls=cls;
		dirPath=path+"/"+cls.getSimpleName();
		File file=new File(dirPath);
		if (!file.exists())
		{
			file.mkdirs();
		}
	}
	public List<T> query(Query query,int start,int end,Filter filter,Sort sort)
	{
		System.out.println("query="+query);
		
		List<T> list=new LinkedList<T>();
		List<ZoieIndexReader<IndexReader>> readerList = null;
        MultiReader multiReader = null;
        IndexSearcher indexSearcher = null;
        ZoieSystem<IndexReader, T> zoieSystem=null;
        TopDocs topDocs = null;
        try {
			
             File idxDir = new File(dirPath);
             // my
             ZoieIndexableInterpreter<T> interpreter = new ZoieDataInterpreter<T>();
             //default
             DefaultIndexReaderDecorator decorator = new DefaultIndexReaderDecorator();
             
             ZoieConfig zoieConfig = new ZoieConfig();
             zoieConfig.setBatchDelay(1000);
             zoieConfig.setBatchSize(5000);
             zoieConfig.setAnalyzer(getAnalyzer());//
             zoieConfig.setSimilarity(new DefaultSimilarity());//
             zoieConfig.setRtIndexing(true);
             zoieSystem = new ZoieSystem(idxDir, interpreter, decorator, zoieConfig);
             zoieSystem.start();
            //zoieSystem.getAdminMBean();
             zoieSystem.getAdminMBean().flushToMemoryIndex();
             
             //
             readerList = zoieSystem.getIndexReaders();
        	 System.out.println("zoieReaderList.size="+readerList.size());
        	 if (readerList.size() == 0)
        	 {
        		 try {
					ZoieIndexReader<IndexReader> myIndexReader=new ZoieDataIndexReader(IndexReader.open(FSDirectory.open(idxDir), true), decorator);
					 readerList.add(myIndexReader);
				} catch (Exception e) 
				{
					System.out.println("Index path not found:"+idxDir.getAbsolutePath());
					//e.printStackTrace();
					return list;
				}
				System.out.println("zoieReaderList.size1="+readerList.size());
        	 }
             multiReader = new MultiReader(readerList.toArray(new IndexReader[readerList.size()]), false);
             indexSearcher = new IndexSearcher(multiReader);
             indexSearcher.setSimilarity(new DefaultSimilarity());

	        // topDocs = indexSearcher.search(query, filter, end,sort);
	         if (sort != null)
             {
            	 topDocs = indexSearcher.search(query, filter, end,sort);
			} else {
				topDocs = indexSearcher.search(query, filter, end);
			}
	         
             total=topDocs.totalHits;
             System.out.println("totalHits="+total);
             ScoreDoc[] docs= topDocs.scoreDocs;
             int iStart=start;
             int iEnd=end;
             if(iEnd > total)
             {
             	iEnd=total;
             }
             
             for (int i = iStart; i < iEnd; i++)
             {
            	ScoreDoc doc=docs[i];
            	Document document=indexSearcher.doc(doc.doc);
            	list.add(transfer(document));
             }
        	
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
            try {
                if (null != indexSearcher) 
                {
                    indexSearcher.close();
                    indexSearcher = null;
                }
                if (null != multiReader) 
                {
                    multiReader.close();
                    multiReader = null;
                }
                if (null != readerList && !readerList.isEmpty()) 
                {
                   zoieSystem.returnIndexReaders(readerList);
                   readerList = null;
                }
                if (zoieSystem !=null)
                {
                	zoieSystem.shutdown();
                	zoieSystem.stop();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
		
		return list;
	}
	public List<T> query(String fieldName,String fieldValue,boolean isAnalyzed,int start,int end,Filter filter,Sort sort)
	{
		BooleanQuery query=new BooleanQuery();
		if (isAnalyzed) 
		{
			fieldValue=fieldValue.replace(" ", "");
			PhraseQuery phraseQuery=new PhraseQuery();
			phraseQuery.setSlop(0);
			for(int j=0;j<fieldValue.length();j++)
			{
				phraseQuery.add(new Term(fieldName,fieldValue.charAt(j)+""));
			}
			query.add(phraseQuery,Occur.MUST);
		} else
		{
			query.add(new TermQuery(new Term(fieldName,fieldValue)),Occur.MUST);
		}
		System.out.println("query="+query);
		
		List<T> list=new LinkedList<T>();
		List<ZoieIndexReader<IndexReader>> readerList = null;
        MultiReader multiReader = null;
        IndexSearcher indexSearcher = null;
        ZoieSystem<IndexReader, T> zoieSystem=null;
        TopDocs topDocs = null;
        try {
			
             File idxDir = new File(dirPath);
            
             // my
             ZoieIndexableInterpreter<T> interpreter = new ZoieDataInterpreter<T>();
             //default
             DefaultIndexReaderDecorator decorator = new DefaultIndexReaderDecorator();
             
             //PerFieldAnalyzerWrapper(new IKAnalyzer());//
            // Analyzer analyzer = new  StandardAnalyzer(Version.LUCENE_35);
             ZoieConfig zoieConfig = new ZoieConfig();
             zoieConfig.setBatchDelay(1000);
             zoieConfig.setBatchSize(5000);
             zoieConfig.setAnalyzer(getAnalyzer());//
             zoieConfig.setSimilarity(new DefaultSimilarity());//
             zoieConfig.setRtIndexing(true);
             zoieSystem = new ZoieSystem(idxDir, interpreter, decorator, zoieConfig);
             zoieSystem.start();
            //zoieSystem.getAdminMBean();
             zoieSystem.getAdminMBean().flushToMemoryIndex();
             
             //
             readerList = zoieSystem.getIndexReaders();
        	 System.out.println("zoieReaderList.size="+readerList.size());
        	 if (readerList.size() == 0)
        	 {
        		 try {
					ZoieIndexReader<IndexReader> myIndexReader=new ZoieDataIndexReader(IndexReader.open(FSDirectory.open(idxDir), true), decorator);
					 readerList.add(myIndexReader);
				} catch (Exception e) 
				{
					System.out.println("Index path not found:"+idxDir.getAbsolutePath());
					//e.printStackTrace();
					return list;
				}
				System.out.println("zoieReaderList.size1="+readerList.size());
        	 }
             multiReader = new MultiReader(readerList.toArray(new IndexReader[readerList.size()]), false);
             indexSearcher = new IndexSearcher(multiReader);
             indexSearcher.setSimilarity(new DefaultSimilarity());
             if (sort != null)
             {
            	 topDocs = indexSearcher.search(query, filter, end,sort);
			} else {
				topDocs = indexSearcher.search(query, filter, end);
			}
	         
             total=topDocs.totalHits;
             System.out.println("totalHits="+total);
             ScoreDoc[] docs= topDocs.scoreDocs;
             int iStart=start;
             int iEnd=end;
             if(iEnd > total)
             {
             	iEnd=total;
             }
             
             for (int i = iStart; i < iEnd; i++)
             {
            	ScoreDoc doc=docs[i];
            	Document document=indexSearcher.doc(doc.doc);
            	list.add(transfer(document));
             }
        	
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
            try {
                if (null != indexSearcher) 
                {
                    indexSearcher.close();
                    indexSearcher = null;
                }
                if (null != multiReader) 
                {
                    multiReader.close();
                    multiReader = null;
                }
                if (null != readerList && !readerList.isEmpty()) 
                {
                   zoieSystem.returnIndexReaders(readerList);
                   readerList = null;
                }
                if (zoieSystem !=null)
                {
                	zoieSystem.shutdown();
                	zoieSystem.stop();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
		
		return list;
	}
	/**
	 * NumericRangeQuery<Integer> rangeQuery=NumericRangeQuery.newIntRange("age", 10, 30, true, true);
	 * @param document
	 * @return
	 */
	public List<T> queryRangeInt(String fieldName,int min,int max,boolean minInclusive,boolean maxInclusive,int start,int end,Filter filter,Sort sort,Query otherQuery)
	{
		Query query=null;
		if (otherQuery == null)
		{
			query=NumericRangeQuery.newIntRange(fieldName, min,max, minInclusive, maxInclusive);
		} else 
		{
			NumericRangeQuery<Integer> rangeQuery=NumericRangeQuery.newIntRange(fieldName, min,max, minInclusive, maxInclusive);
			BooleanQuery booleanQuery=new BooleanQuery();
			booleanQuery.add(otherQuery, Occur.MUST);
			booleanQuery.add(rangeQuery, Occur.MUST);
			query=booleanQuery;
		}
		return query(query, start, end, filter, sort);
	}
	public List<T> queryRangeLong(String fieldName,long min,long max,boolean minInclusive,boolean maxInclusive,int start,int end,Filter filter,Sort sort,Query otherQuery)
	{
		Query query=null;
		if (otherQuery == null)
		{
			query=NumericRangeQuery.newLongRange(fieldName, min,max, minInclusive, maxInclusive);
		} else 
		{
			NumericRangeQuery<Long> rangeQuery=NumericRangeQuery.newLongRange(fieldName, min,max, minInclusive, maxInclusive);
			BooleanQuery booleanQuery=new BooleanQuery();
			booleanQuery.add(otherQuery, Occur.MUST);
			booleanQuery.add(rangeQuery, Occur.MUST);
			query=booleanQuery;
		}
		return query(query, start, end, filter, sort);
	}
	public List<T> queryRangeFloat(String fieldName,float min,float max,boolean minInclusive,boolean maxInclusive,int start,int end,Filter filter,Sort sort,Query otherQuery)
	{
		Query query=null;
		if (otherQuery == null)
		{
			query=NumericRangeQuery.newFloatRange(fieldName, min,max, minInclusive, maxInclusive);
		} else 
		{
			NumericRangeQuery<Float> rangeQuery=NumericRangeQuery.newFloatRange(fieldName, min,max, minInclusive, maxInclusive);
			BooleanQuery booleanQuery=new BooleanQuery();
			booleanQuery.add(otherQuery, Occur.MUST);
			booleanQuery.add(rangeQuery, Occur.MUST);
			query=booleanQuery;
		}
		return query(query, start, end, filter, sort);
	}
	public List<T> queryRangeDouble(String fieldName,double min,double max,boolean minInclusive,boolean maxInclusive,int start,int end,Filter filter,Sort sort,Query otherQuery)
	{
		Query query=null;
		if (otherQuery == null)
		{
			query=NumericRangeQuery.newDoubleRange(fieldName, min,max, minInclusive, maxInclusive);
		} else 
		{
			NumericRangeQuery<Double> rangeQuery=NumericRangeQuery.newDoubleRange(fieldName, min,max, minInclusive, maxInclusive);
			BooleanQuery booleanQuery=new BooleanQuery();
			booleanQuery.add(otherQuery, Occur.MUST);
			booleanQuery.add(rangeQuery, Occur.MUST);
			query=booleanQuery;
		}
		return query(query, start, end, filter, sort);
	}
	public T transfer(Document document)
	{
		T t=null;
			try {
				t=(T) Class.forName(getCls().getName()).newInstance();
				Field[] fields=getCls().getDeclaredFields();
				Field field=null;
				String fieldName="",typeName="";
				Method method=null;
				Object value=null;
				PropertyDescriptor descriptor=null;
				for (int j = 0; j < fields.length; j++) 
				{
					field=fields[j];
					fieldName=field.getName();
					value=document.get(fieldName);
					if (value != null)
					{
						try {
							descriptor= new PropertyDescriptor(fieldName, cls);
							typeName=field.getType().getSimpleName().toLowerCase();
							method = descriptor.getWriteMethod();
							if ("int".equals(typeName)||"integer".equals(typeName))
							{
								value=Integer.parseInt(value+"");
							} else if ("long".equals(typeName))
							{
								value=Long.parseLong(value+"");
							}
						} catch (Exception e) {
							System.out.println(fieldName+"="+value);
							e.printStackTrace();
						}
						method.invoke(t,value);
					}
					 
				}//end for j
				 
			} catch (Exception e) {
				e.printStackTrace();
			}
		return t;
	}
	
	public int getTotal() {
		return total;
	}
	public Analyzer getAnalyzer() 
	{
		if (analyzer==null)
		{
			analyzer=new WhitespaceAnalyzer(Version.LUCENE_35);
		}
		return analyzer;
	}
	public void setAnalyzer(Analyzer analyzer) {
		this.analyzer = analyzer;
	}
	public Class<T> getCls() {
		return cls;
	}
	public String getDirPath() {
		return dirPath;
	}
}
